package cn.flying.cloud.file.core.oss;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;

import cn.flying.cloud.base.oss.exception.OssException;
import cn.flying.cloud.base.oss.model.BucketInfo;
import cn.flying.cloud.base.oss.model.BucketObjectInfo;
import cn.flying.cloud.base.oss.template.OssTemplate;

/**
 * 基于aws的s3协议
 *
 * @author: admin
 * @date: 2023年07月13日 18:00
 * @version: 1.0
 */
public class AwsS3OssTemplate implements DisposableBean, OssTemplate {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final AmazonS3 amazonS3;

    public AwsS3OssTemplate(AmazonS3 amazonS3) {
        this.amazonS3 = amazonS3;
    }

    /**
     * 创建bucket
     *
     * @param bucket bucket名称
     * @return
     * @throws Exception
     */
    @Override
    public boolean createBucket(String bucket) throws OssException {
        try {
            if (amazonS3.doesBucketExistV2(bucket)) {
                return false;
            }
            amazonS3.createBucket(bucket);
            logger.info("创建bucket[{}]成功！", bucket);
            return true;
        } catch (Exception e) {
            logger.error("创建bucket[{}]失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("创建bucket[" + bucket + "]失败！", e);
        }
    }

    /**
     * 获取所有的bucket
     *
     * @return
     * @throws OssException
     */
    @Override
    public List<BucketInfo> getAllBuckets() throws OssException {
        try {
            List<Bucket> buckets = amazonS3.listBuckets();
            List<BucketInfo> list = buckets.stream().map(bucket -> new BucketInfo(bucket.getName(), bucket.getCreationDate())).collect(Collectors.toList());
            return list;
        } catch (Exception e) {
            logger.error("获取所有的bucket失败！错误信息：[{}]", e.getMessage());
            throw new OssException("获取所有的bucket失败！", e);
        }
    }

    /**
     * 通过bucket名称删除bucket
     *
     * @param bucket bucket名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean deleteBucket(String bucket) throws OssException {
        try {
            amazonS3.deleteBucket(bucket);
            logger.info("删除bucket[{}]成功！", bucket);
            return true;
        } catch (Exception e) {
            logger.error("删除bucket[{}]失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("删除bucket[" + bucket + "]失败！", e);
        }
    }

    /**
     * 判断bucket是否存在
     *
     * @param bucket bucket名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean bucketExists(String bucket) throws OssException {
        try {
            return amazonS3.doesBucketExistV2(bucket);
        } catch (Exception e) {
            logger.error("获取bucket[{}]状态失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("获取bucket[" + bucket + "]状态失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket bucket名称
     * @param file   文件对象
     * @return
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, File file) throws OssException {
        String objectName = UUID.randomUUID() + file.getName().substring(file.getName().lastIndexOf("."));
        try (InputStream stream = Files.newInputStream(file.toPath())) {
            putObject(bucket, objectName, stream, stream.available(), "");
            return objectName;
        } catch (Exception e) {
            throw new OssException("文件对象[{" + file.getName() + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param stream     文件流对象
     * @return
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, String objectName, InputStream stream) throws OssException {
        try {
            String name = UUID.randomUUID() + objectName.substring(objectName.lastIndexOf("."));
            putObject(bucket, name, stream, stream.available(), "");
            return name;
        } catch (Exception e) {
            throw new OssException("文件对象[{" + objectName + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket      bucket名称
     * @param objectName  对象名称
     * @param stream      文件流对象
     * @param contextType 文件类型
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, String objectName, InputStream stream, String contextType) throws OssException {
        try {
            String name = UUID.randomUUID() + objectName.substring(objectName.lastIndexOf("."));
            putObject(bucket, name, stream, stream.available(), contextType);
            return name;
        } catch (Exception e) {
            throw new OssException("文件对象[{" + objectName + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 通过objectName从bucket中删除对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean deleteObject(String bucket, String objectName) throws OssException {
        try {
            amazonS3.deleteObject(bucket, objectName);
            logger.info("从bucket[{}]删除文件对象[{}]成功！", bucket, objectName);
            return true;
        } catch (Exception e) {
            logger.error("从bucket[{}]删除文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]删除文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public BucketObjectInfo getObject(String bucket, String objectName) throws OssException {
        try {
            S3Object s3Object = amazonS3.getObject(bucket, objectName);

            BucketObjectInfo objectInfo = new BucketObjectInfo();
            objectInfo.setBucket(s3Object.getBucketName());
            objectInfo.setRegion("");
            objectInfo.setName(s3Object.getKey());
            objectInfo.setContent(IOUtils.toByteArray(s3Object.getObjectContent()));
            return objectInfo;
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public byte[] getObjectBytes(String bucket, String objectName) throws OssException {
        try (S3ObjectInputStream stream = amazonS3.getObject(bucket, objectName).getObjectContent()) {
            return IOUtils.toByteArray(stream);
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param output     输出流对象
     * @return
     * @throws OssException
     */
    @Override
    public void getObject(String bucket, String objectName, OutputStream output) throws OssException {
        try (S3ObjectInputStream stream = amazonS3.getObject(bucket, objectName).getObjectContent()) {
            IOUtils.copy(stream, output);
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取对象的url，默认过期时间7天
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public String getObjectUrl(String bucket, String objectName) throws OssException {
        try {
            Date date = DateUtils.addDays(new Date(), 7);
            URL url = amazonS3.generatePresignedUrl(bucket, objectName, date);
            return url.toString();
        } catch (Exception e) {
            logger.error("从bucket[{}]获取文件对象[{}]地址失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]获取文件对象[{" + objectName + "}]地址失败！", e);
        }
    }

    /**
     * 获取对象的url
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param expiry     过期时间 (minio最小1 second，最大7 days)
     * @param timeUnit   时间单位
     * @return
     * @throws OssException
     */
    @Override
    public String getObjectUrl(String bucket, String objectName, Integer expiry, TimeUnit timeUnit) throws OssException {
        try {
            Date d = new Date();
            Date date;
            switch (timeUnit) {
                case DAYS:
                    date = DateUtils.addDays(d, expiry);
                    break;
                case HOURS:
                    date = DateUtils.addHours(d, expiry);
                    break;
                case MINUTES:
                    date = DateUtils.addMinutes(d, expiry);
                    break;
                case SECONDS:
                    date = DateUtils.addSeconds(d, expiry);
                    break;
                case MILLISECONDS:
                    date = DateUtils.addMilliseconds(d, expiry);
                    break;
                default:
                    date = DateUtils.addDays(new Date(), 7);
            }

            URL url = amazonS3.generatePresignedUrl(bucket, objectName, date);
            return url.toString();
        } catch (Exception e) {
            logger.error("从bucket[{}]获取文件对象[{}]地址失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]获取文件对象[{" + objectName + "}]地址失败！", e);
        }
    }

    /**
     * 从bucket中获取指定前缀prefix的对象集合
     *
     * @param bucket    bucket名称
     * @param prefix    前缀
     * @param recursive 是否递归查询
     * @return
     * @throws OssException
     */
    @Override
    public List<BucketObjectInfo> getAllObjectsByPrefix(String bucket, String prefix, boolean recursive) throws OssException {
        return null;
    }

    /**
     * 上传文件对象
     *
     * @param bucket
     * @param objectName
     * @param stream
     * @param size
     * @param contextType
     */
    private void putObject(String bucket, String objectName, InputStream stream, long size, String contextType) throws Exception {
        if (!bucketExists(bucket)) {
            createBucket(bucket);
        }
        try {
            byte[] bytes = IOUtils.toByteArray(stream);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(size);
            if (StringUtils.hasValue(contextType)) {
                metadata.setContentType(contextType);
            }
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            // 上传
            PutObjectResult object = amazonS3.putObject(bucket, objectName, byteArrayInputStream, metadata);

            logger.info("文件对象[{}]上传到[{}]成功！返回信息：[{}]", objectName, bucket, object);
        } catch (Exception e) {
            logger.error("文件对象[{}]上传到[{}]失败！错误信息：[{}]", objectName, bucket, e.getMessage());
            throw e;
        }
    }


    /**
     * 关闭客户端
     */
    public void close() {
        amazonS3.shutdown();
        logger.info("关闭AmazonS3Client客户端连接！");
    }

    /**
     *
     */
    @Override
    public void destroy() throws OssException {
        close();
    }
}
